#include "cryptography.h"

#include <string>
using std::string;

using namespace std;

cryptography::cryptography(){}

void cryptography::create_key(QString key_name){
    QString namePubKey="e";
    QString namePriKey="d";

    namePubKey.append(key_name);
    namePriKey.append(key_name);

    InvertibleRSAFunction params;
    params.GenerateRandomWithKeySize( rng, 1024);
    RSA::PrivateKey pri( params );

    RSA::PublicKey pub(pri);

    SavePublicKey(namePubKey,pub);
    SavePrivateKey(namePriKey,pri);
}

QString cryptography::encryptMsg(QString Data,QString keyName){
    QString out;
    string plainMsg,encryptedMsg,buf;

//Convierto de QString a std::string
//En estos métodos es importantísimo porque si se usa QString directamente, corta el mensaje y despues no se puede desencriptar
    plainMsg=Data.toStdString();

    RSA::PublicKey pub;

    LoadPublicKey(keyName,pub);

    RSAES_OAEP_SHA_Encryptor e( pub);

    while(plainMsg.size()>0){
        buf.clear();
        if(plainMsg.size()<=86){
            buf=plainMsg;
            plainMsg.clear();

        }
        else{
            for(int j=0;j<86; j++){
                buf+=plainMsg.at(j);
            }
            plainMsg.erase(0,86);
        }

        StringSource(buf, true,
            new PK_EncryptorFilter( rng, e,
                new StringSink( encryptedMsg )
            ) // PK_EncryptorFilter
         ); // StringSource
    }

    return QString::fromStdString(encryptedMsg);
}

bool cryptography::decryptMsg(QString Data,QString keyName, QString &decryptedMsg){
    string decrypted_Msg,buf;
    RSA::PrivateKey pri;

    LoadPrivateKey(keyName,pri);

    RSAES_OAEP_SHA_Decryptor d( pri );

    //Convierto de QString a std::string
    //En estos métodos es importantísimo porque si se usa QString directamente, corta el mensaje y no se puede desencriptar
    try{
        while(Data.size()>0){
            buf=QString(shift_String(Data,128)).toStdString();

            StringSource(buf, true,
                new PK_DecryptorFilter( rng, d,
                    new StringSink( decrypted_Msg )
                ) // PK_DecryptorFilter
             ); // StringSource
        }
    }
    catch(...){
        return false;
    }

    decryptedMsg = QString::fromStdString(decrypted_Msg);
    return true;
}

void cryptography::SavePrivateKey(QString filename, const PrivateKey &key){
    ByteQueue queue;
    key.Save(queue);

    Save(filename, queue);
}

void cryptography::SavePublicKey(QString filename, const PublicKey &key){
    ByteQueue queue;
    key.Save(queue);

    Save(filename, queue);
}

void cryptography::Save(QString filename, const BufferedTransformation &bt){
    filename.prepend("/");
    filename.prepend(QDir::currentPath());

    FileSink file(filename.toAscii().data());

    bt.CopyTo(file);
    file.MessageEnd();
}

void cryptography::LoadPublicKey(QString filename, PublicKey &key){
    ByteQueue queue;

    Load(filename, queue);

    key.Load(queue);
}

void cryptography::LoadPrivateKey(QString filename, PrivateKey &key){
    ByteQueue queue;
    Load(filename, queue);
    key.Load(queue);
}

void cryptography::Load(QString filename, BufferedTransformation& bt){
    filename.prepend("/");
    filename.prepend(QDir::currentPath());

    FileSource file(filename.toAscii().data(), true /*pumpAll*/);
    file.TransferTo(bt);

    bt.MessageEnd();
}

QString cryptography::Hash(QString B, QString keyname){
    // Abre el archivo keyname y carga su contenido en un QByteArray BA_key2
        QFile file(keyname);
        file.open(QIODevice::ReadOnly);
        QByteArray BA_key = file.readAll();
        file.close();

    // Genera la funcion hash de las llaves key1 y key2
        SHA sha;
        string output;

        sha.Update((byte const*)B.toAscii().data(), B.size());
        sha.Update((byte const*)BA_key.data(), BA_key.size());

        SecByteBlock sbbDigest(sha.DigestSize());

        sha.Final(sbbDigest.begin());
        HexEncoder enc(new StringSink(output));
        enc.Put(sbbDigest.begin(), sbbDigest.size());
        enc.MessageEnd();

        return QString::fromStdString(output);
}

QString cryptography::HashKeys(QString keyname1, QString keyname2){
// Abre el archivo key1 y carga su contenido en un QByteArray BA_key1
    QFile file1(keyname1);
    file1.open(QIODevice::ReadOnly);
    QByteArray BA_key1 = file1.readAll();
    file1.close();

// Abre el archivo key2 y carga su contenido en un QByteArray BA_key2
    QFile file2(keyname2);
    file2.open(QIODevice::ReadOnly);
    QByteArray BA_key2 = file2.readAll();
    file2.close();

// Genera la funcion hash de las llaves key1 y key2
    SHA sha;
    string output;

    sha.Update((byte const*)BA_key1.data(), BA_key1.size());
    sha.Update((byte const*)BA_key2.data(), BA_key2.size());

    SecByteBlock sbbDigest(sha.DigestSize());

    sha.Final(sbbDigest.begin());
    HexEncoder enc(new StringSink(output));
    enc.Put(sbbDigest.begin(), sbbDigest.size());
    enc.MessageEnd();

    return QString::fromStdString(output);
}

QString cryptography::shift_String(QString &msg){
    QString buffer;

    int shift = msg.indexOf("&&");

    for(int j=0;j<shift; j++){
        buffer.append(msg.at(j));
    }

    msg.remove(0,shift);
    msg.remove(0,2); // borra &&
    return buffer;
}

QString cryptography::shift_String(QString &msg,int max){
    QString buffer;

    for(int j=0;j<max; j++){
        buffer.append(msg.at(j));
    }

    msg.remove(0,max);
    return buffer;
}

bool cryptography::Key2Hex(QString keyname, QString &keyHex){
    QFile file(keyname);
    if(!file.open(QIODevice::ReadOnly)){
        return false;}

    QByteArray BA = file.readAll();

    string encodedKey;

    HexEncoder hex(new StringSink(encodedKey));
    hex.Put((const byte*)BA.data(),BA.size());
    hex.MessageEnd();

    keyHex=QString::fromStdString(encodedKey);

    return true;
}

bool cryptography::Hex2Key(QString keydata, QString keyname){
    QFile file(keyname);
    if(!file.open(QIODevice::WriteOnly)){
        return false;}

//    QByteArray BA = file.readAll();

    string decodedKey;

    HexDecoder deco(new StringSink(decodedKey));
    deco.Put((byte*)keydata.toStdString().data(),keydata.size());
    deco.MessageEnd();

    file.write(decodedKey.data(),decodedKey.size());

    file.close();
    return true;
}

bool cryptography::M1(QString PVID, QString &m1){
    QString Ek,Eb,es,hexKey;

//OJO****************FALTA IMPLEMENTAR*****************************
//***********************************************
    Eb=QString::number(PVID.size());
    Eb.append("&&");
//*********************************************

// Cargo la llave local que se utiliza para comunicarse con el Key Generator, y la encripto
    if(!Key2Hex("ev",hexKey)){
        return false;
    }

    Ek=encryptMsg(hexKey,"ek");
    Eb.append(QString::number(Ek.size())).append("&&");

// Cargo la llave local que se utiliza para comunicarse con el Ballot Generator
    if(!Key2Hex("es",hexKey)){
        return false;
    }

    Eb.append(PVID).append(Ek).append(hexKey);
    m1=encryptMsg(Eb,"eb");

    return true;
}

bool cryptography::M5(QString &m5, QString &B){
/*  Decriptar M5
    Verifica el mensaje con la llave publica del BG
    Obtiene M3,B y eb
    Verifica que eb es igual a la llave publica de BG almacenada
**    Calcula H(B,es) y lo chequea en el BGBB
    Decrypta M3
    Verifica M3 con la llave publica del KG
    Calcula H(ev,ez) y lo chequea en el KGBB
    Si todo resulta bien, Despliega B y guarda la llave ez
 */
    QString Es,Db,M3,ev, hexKey,Dk,ez;
    int M3_size,B_size;

// Decripto m5 con la llave publica "es"
    if(!decryptMsg(m5,"ds",Es)){
        return false;
    }

// Verifica la firma de la informacion de m5 con la llave publica de BG
    if(!verifyMsg(Es,"eb",Db)){
        return false;
    }

// Obtiene M3,B y eb, además verifica que eb es igual a la llave publica de BG almacenada
    M3_size=shift_String(Db).toInt();
    B_size=shift_String(Db).toInt();

    M3 = shift_String(Db,M3_size);
    B = shift_String(Db,B_size);

    if(!Key2Hex("eb",hexKey)){
        return false;
    }

    if (Db != hexKey){
        return false;
    }

// Decripta M3
    if(!decryptMsg(M3,"dv",ev)){
        return false;}

    int ev_size = shift_String(ev).toInt();
    Dk = shift_String(ev,ev_size);

    if(!Key2Hex("ev",hexKey)){
        return false;
    }

    if (ev != hexKey){
        return false;
    }

    if(!verifyMsg(Dk,"ek",ez)){
        return false;
    }

    if(!Hex2Key(ez,"ez")){
        return false;
    }

    return true;


}

bool cryptography::verifyMsg(QString msg,QString keyToUse, QString &verifiedMsg){
    string signedMsg, verified_Msg;

    signedMsg=msg.toStdString();

    RSA::PublicKey pub;
    LoadPublicKey(keyToUse,pub);

//    CryptoPP::RSASSA_PKCS1v15_SHA_Verifier verifier(pub);
    RSASS<PSSR, SHA>::Verifier verifier(pub);

    try{
        StringSource(signedMsg, true,
            new SignatureVerificationFilter(
                verifier,
                new StringSink(verified_Msg),
                SignatureVerificationFilter::THROW_EXCEPTION | SignatureVerificationFilter::PUT_MESSAGE
           ) // SignatureVerificationFilter
        ); // StringSource
    } catch(Exception e){
        return false;
    }
    verifiedMsg=QString::fromStdString(verified_Msg);

return true;
}

QString cryptography::PVID_generation(QString Data,QString Key){
    string output;

    HMAC<SHA> h((const byte*)Key.toAscii().data(),Key.size());

    byte abMac[h.DIGESTSIZE];

    h.CalculateDigest(abMac,(const byte*)Data.toAscii().data(),Data.size());

    HexEncoder enc(new StringSink(output));
    enc.Put(abMac, h.DIGESTSIZE);
    enc.MessageEnd();

    return QString::fromStdString(output);
}

QString cryptography::Hash(QString data){
// Genera la funcion hash de las llaves key1 y key2
    SHA sha;
    string output;

    sha.Update((byte const*)data.toAscii().data(), data.size());

    SecByteBlock sbbDigest(sha.DigestSize());

    sha.Final(sbbDigest.begin());
    HexEncoder enc(new StringSink(output));
    enc.Put(sbbDigest.begin(), sbbDigest.size());
    enc.MessageEnd();

    return QString::fromStdString(output);
}
